
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
</body>
</html>
<script>
 //手写一个call函数 实现call   
       //通过传递的函数 保存到参数的属性中 
        //适用arguments来接收参数来传递 
        //指向调用函数this指向window 
        //指向完函数记得删除对象上的函数属性 
        var obj = {
            name:'daes',
            age:12
        }
        function myFn(){
            console.log(this.name)
        }

        (()=>{
            function myCall(obj){
                //1 先判断有没有第一个参数没有就执行window
                obj = obj?obj:Window
                //this指向函数 
                obj.fn = this //2 把函数放在对象上
                //获取参数
                let args = [...arguments].slice(1) //选择第一个参数后面的所有的参数 
                //执行函数
                obj.fn(args)
                //删除obj上的函数属性
                delete obj.fn
            }
            //挂载到原型上面 
            Function.prototype.myCall = myCall
        })()
        myFn.myCall(obj)



//  call/apply 
//  用来改变函数内部的this 指向  
//  特点任何函数都可以调用他 说明他们是添加在函数原型上的方法 

//  调用call apply的函数会立即执行  call和apply的返回值 就是函数的返回值  

console.dir(Function.prototype)
        /*
        myCall: ƒ myCall(obj)       
        apply: ƒ apply()            ? 
        arguments: (...)
        bind: ƒ bind()              ?
        call: ƒ call()              ?
        caller: (...)   
        constructor: ƒ Function()
        length: 0
        name: ""
        toString: ƒ toString()
        Symbol(Symbol.hasInstance): ƒ [Symbol.hasInstance]()
        get arguments: ƒ ()
        set arguments: ƒ ()
        get caller: ƒ ()
        set caller: ƒ ()
        [[FunctionLocation]]: ​
        [[Prototype]]: Object
        [[Scopes]]: Scopes[0] */


        var name = '一尾流莺'
        var obj = {
            name: 'warbler',
        }
        function foo() {
            console.log(this.name);    // warbler
            return 'success'
        }
        foo.call(obj)               
        console.log(foo.call(obj));    // success


        // 调用 call 和 apply 指向 undefined 或者 null ，会将 this 指向 window
        function fun() {
             console.log(this)
        }
        fun.call(undefined)
        fun.call(null)
        fun.apply(undefined)
        fun.apply(null)
        /*
            Window {window: Window, self: Window, document: document, name: '一尾流莺', location: Location, …
            this指向3.html:86 Window {window: Window, self: Window, document: document, name: '一尾流莺
            this指向3.html:86 Window {window: Window, self: Window, document: document, name: '一尾流莺', location: Location, …}
            this指向3.html:86 Window {window: Window, self: Window, document: document, name: '一尾流莺', location: Location, …}
        */

        // 调用 call 和 apply 指向一个值类型， 会将 this 指向由它们的构造函数创建的实例。
        function o() {
            console.log(this)
        }
        o.call(11)
        o.call('11')
        o.call(true)
        /*
            Number[[Prototype]]: Number[[PrimitiveValue]]: 11
        this指向3.html:101 String0: "1"1: "1"length: 2[[Prototype]]: String[[PrimitiveValue]]: "11"
        this指向3.html:101 Boolean[[Prototype]]: Boolean[[PrimitiveValue]]: true

        */


        // 调用 call 和 apply 指向一个引用类型， 会将 this 指向这个对象。
        // 声明一个全局变量 names 和一个全局作用域下的函数 foos  
        var names = '一尾流莺'
        var objs = {
             name: 'warbler',
        }
        function foos() {
            console.log(this.name)
        }
        foos()    //  一尾流莺
        /*
            name 等价于 window.name ， foo() 等价于 window.foo() ，我们打印出this.name，当前的 this 指向它的调用者 window， 也就是 window.name 得到 一尾流莺。
            但是如果我想打印出 warbler 该怎么办呢？在 obj 里面再定义一个 obj.fn 么? 当然不需要，

            我们只需要调用 call/apply 改变 this 的指向，指向 obj 这个对象就可以了。这个时候 this.name 等价于 obj.name ，就得到了 warbler 。
        */
        foos.call(objs) //=> warbler
        foos.apply(objs) //=> warbler



        // 手写call / apply 
        /*
            Object()方法 
            如果传入的值类型  会返回对应类型的构造函数创建的实例  
            如果传入的是对象  返回对象本身
            如果传入undeifined或者null 会返回空对象  
        */ 
        var name = '一尾流莺'
        var obj = {
        name: 'warbler',
        }
        function foo() {
        console.dir(this);
        return 'success'
        }

        Function.prototype._call = function(ctx, ...args){
            // 判断上下文this 如果undefined 或 null 指向window 
            // 否则使用 Object() 将上下文包装成对象
            const o = ctx == undefined ? window : Object(ctx)
             // 把函数foo赋值给对象o的一个属性  用这个对象o去调用foo  this就指向了这个对象o
             // 下面的this就是调用_call的函数foo  我们把this给对象o的属性fn 就是把函数foo赋值给了o.fn
             // 给context新增一个独一无二的属性以免覆盖原有属性
             const key = Symbol()
             o[key] = this
             // 立即执行一次  
             const result = o[key](...args)
            // 删除这个属性
            delete o[key]
            // 把函数的返回值赋值给_call的返回值
            return result
        }
        foo._call(undefined) // window
        foo._call(null) // window
        foo._call(1)    // Number
        foo._call('11') // String
        foo._call(true) // Boolean
        foo._call(obj)  // {name: 'warbler'}
        console.log(foo._call(obj)); // success

        // call 和 apply 的唯一区别就是传递参数的不同，所以我们只需要改一下对参数的处理，其它的和 call 一致就可以了。
        var age = 10
        var obj = {
        age: 20,
        }
        function foo(a, b) {
         console.dir(this.age + a + b);
        }
        // 只需要把第二个参数改成数组形式就可以了。
        Function.prototype._apply = function(ctx, array = []) {
         const o = ctx == undefined ? window : Object(ctx)
            //给context新增一个独一无二的属性以免覆盖原有属性
            const key = Symbol()
            o[key] = this
            const result = o[key](...array)
            delete o[key]
            return result
        }
        foo(3, 4) // => 17
        foo._apply(obj, [3, 4]) //=> 27





        // bind  
        var name = '一尾流莺'
        var obj = {
            name: 'warbler',
        }
        // this 指向调用者document
        document.onclick = function() {
            console.dir(this); // => #document
        }
        // this 指向 obj
        document.onclick = function() {
            console.dir(this); // => #Object{name:'warbler}
        }.bind(obj)

        Function.prototype._bind = function(ctx, ...args) {
            // 下面的this就是调用_bind的函数,保存给_self
        const _self = this
            // bind 要返回一个函数, 就不会立即执行了
        const newFn = function(...rest) {
            // 调用 call 修改 this 指向
            return _self.call(ctx, ...args, ...rest)
        }
        if (_self.prototype) {
            // 复制源函数的prototype给newFn 一些情况下函数没有prototype，比如箭头函数
            newFn.prototype = Object.create(_self.prototype);
        }
            return newFn
        }

































</script>